package com;

import javax.sql.DataSource;
import java.sql.*;
import java.util.Arrays;

/*
* MiniQueryRunner的父类
* abstract:抽象的 就是这是一个抽象的类，不能直接使用，因为功能不完整
*/
public abstract class MiniAbstractQueryRunner {
    private volatile boolean pmdKnownBroken = false;
    @Deprecated
    protected final DataSource ds;
    //protexted 是受保护的 注解@Deprecated：是不推荐使用 因为这样写这个类的子类都可以调用

    //每次写类的时候都应该有个所有参数的构造函数（就是下面这个），其它的构造函数都是调用它
    //还建议要有无参构造函数
    public MiniAbstractQueryRunner(DataSource ds, boolean pmdKnownBroken) {
        this.pmdKnownBroken = pmdKnownBroken;
        this.ds = ds;
    }

    public MiniAbstractQueryRunner() {
        ds = null;
    }

    public MiniAbstractQueryRunner(boolean pmdKnownBroken){
        this.pmdKnownBroken = pmdKnownBroken;
        ds = null;
    }

    public MiniAbstractQueryRunner(DataSource ds){
        this.ds = ds;
    }


    protected void close(Statement stmt) throws SQLException {
        MiniDbutils.close(stmt);
    }

    protected void close(Connection con) throws SQLException {
        MiniDbutils.close(con);
    }

    protected void close(ResultSet rs) throws SQLException {
        MiniDbutils.close(rs);
    }

    public DataSource getDataSource(){
        return this.ds;
    }

    protected Connection prepareConnection() throws SQLException {
        if(this.getDataSource() == null){
            throw new SQLException("获取连接异常");
        }
        return this.getDataSource().getConnection();
        //以这种方式调用需要一个DataSource，没有则报错，可以通过实例化MiniQueryRunner传递 或者 调用方法时传递一个Connection
    }

    /*
    * 这是 操作数据库时的错误异常处理 将以日志打印出来
    */
    protected void rethrow(SQLException cause,String sql,Object...params)throws SQLException{
        String causeMessage = cause.getMessage();
        if(causeMessage == null){
            causeMessage = "";
        }

        //StringBuffer可以存储和操作字符串
        StringBuffer msg = new StringBuffer(causeMessage);
        msg.append(" Query: ");
        msg.append(sql);
        msg.append(" Parameters: ");
        if(params == null){
            msg.append("[]");
        }else{
            msg.append(Arrays.deepToString(params));
        }

        SQLException e=new SQLException(msg.toString(),cause.getSQLState(),cause.getErrorCode());
        e.setNextException(cause);
        throw e;
    }

    /*
    * 单独弄个一行代码的方法，原因是：
    * 1、利用此方法可以完善对象的创建，可以集中对statement进行一些设置，像超时设置，字段数量等等
    * 2、因为是方法，而且是protected修饰符，所以就给了子类可以重写创建对象
    */
    protected PreparedStatement prepareStatement(Connection con,String sql) throws SQLException {
        PreparedStatement ps=con.prepareStatement(sql);
        return ps;
    }

    /*
    * 增改 填充
    * 用PreparedStatement而不用statement是防止sql注入，防止用户非法输入值
    */
    public void fillStatement(PreparedStatement stmt,Object ... params) throws SQLException {
        //参数源数据简单的理解为是sql语句中“？”的个数
        ParameterMetaData pmd = null;
        if(!pmdKnownBroken){
            try {
                pmd = stmt.getParameterMetaData();
                if(pmd == null){
                    pmdKnownBroken = true;
                }else {
                    int stmtCount = pmd.getParameterCount();
                    //实际参数值的数量的处理，没有值数量就为0
                    int paramsCount = params == null ? 0 :params.length;

                    //问号的数量与实际值的数量不匹配就报错
                    if(stmtCount != paramsCount){
                        throw new SQLException("错误的参数数量，预期是："+stmtCount+"个，给出了"+paramsCount+"个");
                    }
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }

        //如果没有一个参数，就啥也不做 也不会执行下面 return
        if(params == null){
            return;
        }

        for (int i = 0; i < params.length; i++) {
            if(params[i] != null){ //问号的值不为空
                stmt.setObject(i+1,params[i]);
            }else{
                //设置列的空值，是调用setNull（列号，列的类型）这种语法
                // 当无法知道列的类型时，就指定为varchar类型，因为很多的jdbc 驱动是允许这样做的
                // 如果驱动实现了ParameterMetaData的getParameterType方法，那么就用此方法来精确地获取列的类型

                int sqlType = Types.VARCHAR;
                if(!pmdKnownBroken){
                    try {
                        sqlType = pmd.getParameterType(i+1); //获取到了数据类型就使用获取到的
                    } catch (SQLException throwables) {
                        pmdKnownBroken = true;
                    }
                }
                stmt.setNull(i+1,sqlType);
            }

        }
    }
}
